package tinybase.rm;

import java.nio.ByteBuffer;
import java.nio.charset.Charset;

import tinybase.basic.BytePointer;
import tinybase.basic.RedBase;
import tinybase.basic.RedBase.AttrType;
import tinybase.basic.RedBase.ClientHint;
import tinybase.basic.RedBase.CompOp;
import tinybase.basic.RC;
import tinybase.pf.PF_FileHandle;
import tinybase.pf.PF_PageHandle;
import tinybase.pf.PageNum;

public class RM_FileScan {

	private boolean bScanOpen;
	private int curPageNum;
	private int curSlotNum;
	private RM_FileHandle rmFileHandle;
	private AttrType attrType;
	private int attrLength;
	private int attrOffset;
	private CompOp compOp;
	private Object value;
	private ClientHint pinHint;

	//
	// RM_FileScan
	//
	// Desc: Default Constructor
	//
	public RM_FileScan() {

		// Initialize member variables
		bScanOpen = false;
		curPageNum = RM_Internal.RM_HEADER_PAGE_NUM;
		curSlotNum = 0;

		rmFileHandle = null;
		attrType = AttrType.INT;
		attrLength = 4;
		attrOffset = 0;
		compOp = CompOp.NO_OP;
		value = null;
		pinHint = ClientHint.NO_HINT;
	}

	//
	// OpenScan
	//
	// Desc: Open a file scan with the given fileHandle and scan condition
	// In: fileHandle - RM_FileHandle object (must be open)
	// _attrType - INT|FLOAT|STRING
	// _attrLength - 4 for INT|FLOAT, 1~MAXSTRING for STRING
	// _attrOffset - indicates the location of the attribute
	// _compOp - EQ_OP|LT_OP|GT_OP|LE_OP|GE_OP|NE_OP|NO_OP
	// _value - points to the value which will be compared with
	// the given attribute
	// _pinHint - not implemented yet
	// Ret: RM_SCANOPEN, RM_VALUENULL, RM_INVALIDATTR, RM_CLOSEDFILE
	//
	public RC openScan(RM_FileHandle fileHandle, AttrType _attrType,
			int _attrLength, int _attrOffset, CompOp _comOp, Object _value,
			ClientHint _pinHint) {
		
		if (_comOp == null)
			return RC.RM_INVALIDCOMPOP;
		
		// Sanity Check: 'this' should not be open yet
		if (bScanOpen)
			// Test: opened RM_FileScan
			return (RC.RM_SCANOPEN);

		// Sanity Check: fileHandle must be open
		if (fileHandle.fileHdr.getRecordSize() == 0) // a little tricky here
			// Test: unopened fileHandle
			return (RC.RM_CLOSEDFILE);

		// Sanity Check: compOp
		switch (_comOp) {
		case EQ_OP:
		case LT_OP:
		case GT_OP:
		case LE_OP:
		case GE_OP:
		case NE_OP:
		case NO_OP:
			break;

		default:
			return (RC.RM_INVALIDCOMPOP);
		}

		if (_comOp != RedBase.CompOp.NO_OP) {
			// Sanity Check: value must not be NULL
			if (_value == null)
				// Test: null _value
				return (RC.RM_NULLPOINTER);

			// Sanity Check: attrType, attrLength
			switch (_attrType) {
			case INT:
			case FLOAT:
				if (_attrLength != 4)
					// Test: wrong _attrLength
					return (RC.RM_INVALIDATTR);
				break;

			case STRING:
				if (_attrLength < 1 || _attrLength > RedBase.MAXSTRINGLEN)
					// Test: wrong _attrLength
					return (RC.RM_INVALIDATTR);
				break;

			default:
				// Test: wrong _attrType
				return (RC.RM_INVALIDATTR);
			}

			// Sanity Check: attrOffset
			if (_attrOffset < 0
					|| _attrOffset + _attrLength > fileHandle.fileHdr
							.getRecordSize())
				// Test: wrong _attrOffset/_attrLength
				return (RC.RM_INVALIDATTR);
		}

		// Copy parameters to local variable
		rmFileHandle = fileHandle;
		attrType = _attrType;
		attrLength = _attrLength;
		attrOffset = _attrOffset;
		compOp = _comOp;
		value = _value;
		pinHint = _pinHint;

		// Set local state variables
		bScanOpen = true;
		curPageNum = RM_Internal.RM_HEADER_PAGE_NUM;
		curSlotNum = rmFileHandle.fileHdr.getNumRecordsPerPage();

		return RC.SUCCESS;
	}

	// In: fh, refCurPageNum
	// Out: ph, refCurPageNum
	private RC FetchAnotherPage(PF_FileHandle fh, PageNum refCurPageNum,
			PF_PageHandle ph) {
		int _curPageNum = refCurPageNum.getPageNum();
		RC rc = RC.SUCCESS;
		if ((rc = fh.getNextPage(_curPageNum, ph)) != RC.SUCCESS) {
			return rc;
		}
		return (rc = ph.getPageNum(refCurPageNum));
	}

	//
	// GetNextRec
	//
	// Desc: Retrieve a copy of the next record that satisfies the scan
	// condition
	// Out: rec - Set to the next matching record
	// Ret: RM or PF return code
	//
	public RC getNextRec(RM_Record rec) {
		RC rc = RC.SUCCESS;
		PF_PageHandle pageHandle = new PF_PageHandle();
		PageNum refPageNum = new PageNum(PageNum.PF_PAGE_UNSET);
		BytePointer refpData = new BytePointer();
		byte[] pData = null;
		RID curRid = null;

		// Sanity Check: 'this' must be open
		if (!bScanOpen)
			return (RC.RM_CLOSEDSCAN);

		// Sanity Check: fileHandle must be open
		if (rmFileHandle.fileHdr.getRecordSize() == 0) // a little tricky here
			// Test: unopened fileHandle
			return (RC.RM_CLOSEDFILE);

		// Fetch another page if required
		if (curSlotNum == rmFileHandle.fileHdr.getNumRecordsPerPage()) {
			refPageNum.setPageNum(curPageNum);
			if ((rc = FetchAnotherPage(rmFileHandle.pfFileHandle, refPageNum,
					pageHandle)) != RC.SUCCESS) {
				return rc;
			}
			curPageNum = refPageNum.getPageNum();
			// Reset curSlotNum
			curSlotNum = 0;
		}
		// We didn't process the whole page in the previous GetNextRec() call
		else {
			if ((rc = rmFileHandle.pfFileHandle.getThisPage(curPageNum,
					pageHandle)) != RC.SUCCESS) {
				if (rc == RC.PF_INVALIDPAGE) {
					// We can get PF_INVALIDPAGE if curPageNum was disposed
					refPageNum.setPageNum(curPageNum);
					if ((rc = FetchAnotherPage(rmFileHandle.pfFileHandle,
							refPageNum, pageHandle)) != RC.SUCCESS) {
						return rc;
					}
					curPageNum = refPageNum.getPageNum();
					// Reset curSlotNum
					curSlotNum = 0;
				} else {
					// Test: closed fileHandle
					return rc;
				}
			}
		}

		while (true) {
			if ((rc = pageHandle.getData(refpData)) != RC.SUCCESS) {
				// Should not happen
				rmFileHandle.pfFileHandle.unpinPage(curPageNum);
				return rc;
			}
			pData = refpData.getArray();

			// Find the next record based on scan condition
			findNextRecInCurPage(pData);

			// No HIT in this page, go to next page
			if (curSlotNum == rmFileHandle.fileHdr.getNumRecordsPerPage()) {
				// Unpin this page
				if ((rc = rmFileHandle.pfFileHandle.unpinPage(curPageNum)) != RC.SUCCESS)
					// Should not happen
					return rc;

				refPageNum.setPageNum(curPageNum);
				if ((rc = FetchAnotherPage(rmFileHandle.pfFileHandle,
						refPageNum, pageHandle)) != RC.SUCCESS) {
					return rc;
				}
				curPageNum = refPageNum.getPageNum();
				// Reset curSlotNum
				curSlotNum = 0;
			} else {
				break;
			}
		}

		// HIT: copy the record to the given location
		curRid = new RID(curPageNum, curSlotNum);
		rec.setRid(curRid);
		rec.setRecordSize(rmFileHandle.fileHdr.getRecordSize());
		byte[] tmp = new byte[rec.getRecordSize()];
		System.arraycopy(pData, PF_PageHandle.DATASTART_OFFSET
				+ rmFileHandle.fileHdr.getPageHeaderSize() + curSlotNum
				* rmFileHandle.fileHdr.getRecordSize(), tmp, 0,
				rec.getRecordSize());
		rec.setpData(tmp);

		// Increment curSlotNum
		curSlotNum++;

		// If there is no more matching record in this page,
		// we don't have to access this page in the next GetNextRec() call.
		findNextRecInCurPage(pData);

		// Unpin this page
		if ((rc = rmFileHandle.pfFileHandle.unpinPage(curPageNum)) != RC.SUCCESS) {
			// Should not happen
			return rc;
		}

		// Return ok
		return RC.SUCCESS;
	}

//	 public RC getNextRec(RM_Record rec) {
//	
//	 RC rc;
//	 BytePointer refpData = new BytePointer();
//	 byte[] pData = null;
//	 RID curRid = new RID(); //Pointer
//	 PF_PageHandle pageHandle= new PF_PageHandle();
//	 PageNum refPageNum = new PageNum(PageNum.PF_PAGE_UNSET);
//	 int pageNum = PageNum.PF_PAGE_UNSET;
//	
//	 // Sanity Check: 'this' must be open
//	 if (!bScanOpen)
//	 return (RC.RM_CLOSEDSCAN);
//	
//	 // Sanity Check: fileHandle must be open
//	 if (rmFileHandle.fileHdr.getRecordSize() == 0) // a little tricky here
//	 // Test: unopened fileHandle
//	 return (RC.RM_CLOSEDFILE);
//	
//	 // Fetch another page if required
//	 /*
//	 * I change the structure and delete the goto
//	 */
//	 boolean notFirst= false;
//	 boolean stop= false;
//	 do {
//	 if (curSlotNum == rmFileHandle.fileHdr.getNumRecordsPerPage()
//	 || notFirst) {
//	 notFirst= true;
//	 if (pinHint == ClientHint.REVERSE_HINT) {
//	 if (curPageNum == RM_Internal.RM_HEADER_PAGE_NUM) {
//	 if ((rc= rmFileHandle.pfFileHandle.getLastPage(pageHandle))
//	 != RC.SUCCESS)
//	 return rc;
//	 } else {
//	 if ((rc= rmFileHandle.pfFileHandle.getPrevPage(curPageNum, pageHandle))
//	 != RC.SUCCESS)
//	 return rc;
//	 }
//	 } else {
//	 // get next page
//	 if ((rc=rmFileHandle.pfFileHandle.getNextPage(curPageNum, pageHandle))
//	 != RC.SUCCESS)
//	 return rc;
//	 }
//	
//	 // Update curPageNum
//	 if ((rc = pageHandle.getPageNum(refPageNum)) != RC.SUCCESS)
//	 // Should not happen
//	 // In fact, we need to unpin the page, but don't know the page number
//	 return rc;
//	 pageNum = refPageNum.getPageNum();
//	
//	 if (pinHint == ClientHint.REVERSE_HINT && curPageNum ==
//	 RM_Internal.RM_HEADER_PAGE_NUM) {
//	 rc= RC.RM_EOF;
//	 System.out.println("curPageNum1= " + curPageNum);
//	 rmFileHandle.pfFileHandle.unpinPage(curPageNum);
//	 }
//	
//	 // Reset Current slot number
//	 curSlotNum= 0;
//	 } else {
//	 if ((rc= rmFileHandle.pfFileHandle.getThisPage(curPageNum, pageHandle))
//	 != RC.SUCCESS)
//	 if (rc == RC.PF_INVALIDPAGE) {
//	 // We can get PF_INVALIDPAGE if curPageNum was disposed
//	 continue;
//	 } else
//	 return rc;
//	 }
//	
//	 if ((rc= pageHandle.getData(refpData)) != RC.SUCCESS)
//	 // should not happen
//	 return rc;
//	 pData = refpData.getArray();
//	 // Find the next record based on scan condition
//	 //System.out.println("\t\t\tpData is : " + pData.getArray().toString());
//	 // for (int i= 0; i < pData.getArray().length; i++)
//	 // System.out.print(pData.getArray()[i]);
//	 findNextRecInCurPage(pData);
//	
//	 // No HIT in this page, go to next page
//	 if (curSlotNum == rmFileHandle.fileHdr.getNumRecordsPerPage()) {
//	 // Unpin this page
//	 System.out.println("curPageNum2= " + curPageNum);
//	 if ((rc = rmFileHandle.pfFileHandle.unpinPage(curPageNum)) != RC.SUCCESS)
//	 // Should not happen
//	 return rc;
//	 else
//	 continue;
//	 } else
//	 stop = true;
//	 } while (!stop);
//	
//	 // HIT: copy the record to the given location
//	
//	 rec.setRid(new RID(curPageNum, curSlotNum)); // ------------ Need method
////	 setRid -------------
//	 rec.setRecordSize(rmFileHandle.fileHdr.getRecordSize()); //
////	 -------------- Need setter
//	 byte[] tmp = new byte[rmFileHandle.fileHdr.getRecordSize()];
//	 System.arraycopy(pData, PF_PageHandle.DATASTART_OFFSET +
//	 rmFileHandle.fileHdr.getPageHeaderSize()
//	 + curSlotNum * rmFileHandle.fileHdr.getRecordSize(), tmp, 0, tmp.length);
//	 rec.setpData(tmp);
//	
//	 // Increment curSlotNum
//	 curSlotNum++;
//	
//	 // If there is no more matching record in this page,
//	 // we don't have to access this page in the next GetNextRec() call.
//	 findNextRecInCurPage(pData);
//	 System.out.println("curPageNum3= " + curPageNum);
//	 // Unpin this page
//	 if ((rc = rmFileHandle.pfFileHandle.unpinPage(curPageNum)) != RC.SUCCESS)
//	 // Should not happen
//	 return rc;
//	
//	 // Return ok
//	 return RC.SUCCESS;
//	
//	 }

	//
	// FineNextRecInCurPage
	//
	// Desc: Iterates slots in the current page (until hit or end)
	// In: pData - points a data page buffer
	//
	RC findNextRecInCurPage(byte[] pData) {

		int bitmapStartOffset = PF_PageHandle.DATASTART_OFFSET
				+ RM_PageHdr.RM_PageHdr_SIZE;
		for (; curSlotNum < rmFileHandle.fileHdr.getNumRecordsPerPage(); curSlotNum++) {
			int cmp = 0;
			int recordStartOffset = 0;
			int attrStartOffset = 0;

			// Get the bitmap corresponding to the curSlotNum
			if (!(rmFileHandle.getBitmap(pData, bitmapStartOffset, curSlotNum))) {
				continue;
			}

			// Hit if NO_OP
			if (compOp == CompOp.NO_OP)
				break;

			// Do comparison according to the attribute type
			recordStartOffset = PF_PageHandle.DATASTART_OFFSET
					+ rmFileHandle.fileHdr.getPageHeaderSize() + curSlotNum
					* rmFileHandle.fileHdr.getRecordSize();
			attrStartOffset = recordStartOffset + this.attrOffset;
			switch (attrType) {
			case INT:
				int dataInt = ByteBuffer.wrap(pData).getInt(attrStartOffset);
				Integer intData = new Integer(dataInt);
				cmp = intData.compareTo((Integer) value); // Compare the 2 int
				break;

			case FLOAT:
				float dataFloat = ByteBuffer.wrap(pData).getFloat(
						attrStartOffset);
				Float floatData = new Float(dataFloat);
				cmp = (floatData.compareTo((Float) value));
				break;

			case STRING:
				// String dataString = ByteBuffer.wrap(pData).toString();
				byte[] strBuf = new byte[this.attrLength];
				System.arraycopy(pData, attrStartOffset, strBuf, 0,
						this.attrLength);
				String dataString = new String(strBuf, Charset.forName("ASCII"));
				cmp = dataString.compareTo((String) value);
				break;
			}

			// Make decision according to comparison operator
			if ((compOp == CompOp.EQ_OP && cmp == 0)
					|| (compOp == CompOp.LT_OP && cmp < 0)
					|| (compOp == CompOp.GT_OP && cmp > 0)
					|| (compOp == CompOp.LE_OP && cmp <= 0)
					|| (compOp == CompOp.GE_OP && cmp >= 0)
					|| (compOp == CompOp.NE_OP && cmp != 0))
				break;
		}

		return RC.SUCCESS;
	}

	//
	// CloseScan
	//
	// Desc: Close a file scan with the given fileHandle and scan condition
	// Ret: RM_CLOSEDSCAN
	//
	public RC closeScan() {

		// Sanity Check: 'this' must be open
		if (!bScanOpen)
			// Test: closed RM_FileScan
			return (RC.RM_CLOSEDSCAN);

		// Reset member variables
		bScanOpen = false;
		curPageNum = RM_Internal.RM_HEADER_PAGE_NUM;
		curSlotNum = 0;
		rmFileHandle = null;
		attrType = AttrType.INT;
		attrLength = 4; // ---------- Find the const
		attrOffset = 0;
		compOp = CompOp.NO_OP;
		value = null;
		pinHint = ClientHint.NO_HINT;

		// Return ok
		return RC.SUCCESS;
	}

}
